﻿using System;
using System.Linq;
using Asistente.Model.Representation;

namespace Asistente.Model.IFPUG
{
    //Clase encargada de identificar el tipo de las funciones de datos y de las funciones de
    //transaccion.

    public class Identifier
    {
        private static Identifier _instance = new Identifier();

        public static Identifier Instance
        {
            get
            {
                if (_instance == null)
                    _instance = new Identifier();
                return _instance;
            }
        }

        #region DataFunction

        //El tipo de una funcion de datos lo define la propiedad is Mantained de la entidad

        public DataFunctionType Identify(Entity entity)
        {
            //Verificamos si es mantenida en el sistema
            if (entity.IsMaintained)
            {
                return DataFunctionType.Ilf;
            }
            return DataFunctionType.Eif;

        }

        #endregion

        #region TransactionalFunction

        //El tipo de una funcion transaccional esta dado segun el caso de uso. Ademas para algunos
        //casos de uso, una funcion transaccional puede ser de diferentes tipos segun los controles que contenga.
        //Para los casos de uso del tipo Empty, se debe determinar que tipo de funcion transaccional corresponde.

        public TransactionalFunctionType Identify(UseCase useCase)
        {
            UseCasePattern useCasePattern = useCase.Pattern;
            
            switch (useCasePattern)
            {
                case UseCasePattern.WorkWith:
                    // En caso de tener controles que son campos calculados es EO
                    if (HasCalculatedFields(useCase))
                    {
                        return TransactionalFunctionType.Eo;
                    }
                    return TransactionalFunctionType.Eq;

                case UseCasePattern.New:
                    return TransactionalFunctionType.Ei;

                case UseCasePattern.View:
                    return TransactionalFunctionType.Eq;

                case UseCasePattern.LogicDelete:
                    return TransactionalFunctionType.Ei;

                case UseCasePattern.PhysicalDelete:
                    return TransactionalFunctionType.Ei;

                case UseCasePattern.Update:
                    return TransactionalFunctionType.Ei;

                case UseCasePattern.Search:
                    return TransactionalFunctionType.Eq;

                case UseCasePattern.Login:
                    return TransactionalFunctionType.Eq;

                case UseCasePattern.List:
                    // En caso de tener controles que son campos calculados es EO
                    if (HasCalculatedFields(useCase))
                    {
                        return TransactionalFunctionType.Eo;
                    }
                    return TransactionalFunctionType.Eq;

                case UseCasePattern.Configuration:
                    return TransactionalFunctionType.Ei;

                case UseCasePattern.Empty:
                    return IdentifyEmpty(useCase); // Debemos inferirlo

                case UseCasePattern.PasswordChange:
                    return  TransactionalFunctionType.Ei;

                default: // No hay otros casos, solamente por control
                    throw new NotImplementedException();
            }
        }


        private TransactionalFunctionType IdentifyEmpty(UseCase useCase)
        {

            // Si tiene un panel de tipo Form o Grid implica que está haciendo un mantenimiento si no tiene campos de
            // tipo PlainTextOutput. Los mantenimientos son EI sino hay campos de tipo CalculatedField y EO si hay al 
            // menos un campo de tipo CalculatedField. Los reportes son EO si hay al menos un campo de tipo 
            // CalculatedField y es un EQ si no hay ningún campo de tipo CalculatedField.

            // Si tiene un panel de tipo Report, Graph o Filter implica que no es un mantenimiento. El panel de tipo
            // Graph siempre es un EO. Los paneles de tipo Report y Filter son EO si hay al menos un campo de tipo
            // CalculatedField y es un EQ si no hay ningún campo de tipo CalculatedField.

            // -----------------------------------------------------------------------------------------------------

            // Es un EO si hay por lo menos un panel de tipo Form o Grid y en estos paneles al menos un campo de tipo
            // CalculatedField; o si hay por lo menos un panel de tipo Report o Filter y en estos paneles al menos un
            // campo de tipo CalculatedField. O si tiene al menos un panel del tipo Graph

            // -----------------------------------------------------------------------------------------------------

            //En cualquier otro caso se identifica como un EQ

            if (HasAtLeastOneFormOrGridWithCalculatedField(useCase) || HasAtLeastOneReportOrFilterWithCalculatedField(useCase) || this.HasAtLeastOneGraphPanel(useCase))
            {
                return TransactionalFunctionType.Eo;
            }

            // Es un EI si no es un EO y hay por lo menos panel de tipo Form o Grid y en estos paneles no hay ningún
            // campo de tipo CalculatedField.Si contiene al al menos un grid o form solo con plain text output es un EQ

            if (HasAtLeastOneFormOrGridWithoutCalculatedField(useCase) && !HasAtLeastOneFormOrGridWithAllPlainTextOutput(useCase))
            {
                return TransactionalFunctionType.Ei;
            }

            // En otro caso es un EQ.
            return TransactionalFunctionType.Eq;
        }

        #endregion

        #region Utilities

        private bool HasAtLeastOneFormOrGridWithAllPlainTextOutput(UseCase useCase)
        {
            return (from screen in useCase.Screens from panel in screen.Panels where panel.GetType() == typeof(Form) || panel.GetType() == typeof(Grid) select panel).Any(panel => HasOnlyPlainTextOutput(panel));
        }

        private bool HasOnlyPlainTextOutput(Panel panel)
        {
            return panel.Controls.All(control => control.ControlPattern == ControlPattern.PlainTextOutput);
        }

        private bool HasAtLeastOneGraphPanel(UseCase useCase)
        {
            return useCase.Screens.SelectMany(screen => screen.Panels).Any(panel => panel.GetType() == typeof(Graph));
        }

        private bool HasCalculatedFields(UseCase useCase)
        {
            return useCase.Screens.SelectMany(screen => screen.Panels).Any(panel => HasCalculatedFields(panel));
        }

        private bool HasAtLeastOneFormOrGridWithoutCalculatedField(UseCase useCase)
        {
            foreach (Screen screen in useCase.Screens)
            {
                //Caso en que hay un panel del tipo Form o Grid y al menos uno no tiene campos calculados
                if (screen.Panels.Where(panel => panel.GetType() == typeof(Form) || panel.GetType() == typeof(Grid)).Any(panel => !HasCalculatedFields(panel)))
                {
                    return true;
                }

                //Debemos hacer lo mismo pero para todos los detalles
                foreach (MasterDetail masterDetail in screen.MasterDetails)
                {
                    //Un master de tipo form o grid y hay al menos uno que no tiene campos calculados
                    if (masterDetail.Master.GetType() == typeof(Form) || masterDetail.Master.GetType() == typeof(Grid))
                    {
                        if (!HasCalculatedFields(masterDetail.Master))
                        {
                            return true;
                        }
                    }

                    //Para los panales detalles de los master detail que son form o grid hay al menos uno que no tiene campos calculados
                    if (masterDetail.Details.Where(detail => detail.GetType() == typeof(Form) || detail.GetType() == typeof(Grid)).Any(detail => !HasCalculatedFields(detail)))
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        private static bool HasCalculatedFields(Panel panel)
        {
            // True si al menos uno de los controles del panel es del tipo calculated field
            return panel.Controls.Any(control => control.ControlPattern == ControlPattern.CalculatedField);
        }

        private bool HasAtLeastOneReportOrFilterWithCalculatedField(UseCase useCase)
        {
            foreach (Screen screen in useCase.Screens)
            {
                //Para los paneles del tipo Report o Filter, si al menos uno tiene campos calculados
                if (screen.Panels.Where(panel => panel.GetType() == typeof(Report) || panel.GetType() == typeof(Filter)).Any(panel => HasCalculatedFields(panel)))
                {
                    return true;
                }

                //Debemos hacer lo mismo pero para todos los detalles
                foreach (MasterDetail masterDetail in screen.MasterDetails)
                {
                    //Un master de tipo filter o reprot y hay al menos uno que no tiene campos calculados
                    if (masterDetail.Master.GetType() == typeof(Report) || masterDetail.Master.GetType() == typeof(Filter))
                    {
                        if (HasCalculatedFields(masterDetail.Master))
                        {
                            return true;
                        }
                    }

                    //Para los paneles de los detalles del tipo report o filter, si hay al menos uno que tiene campos calculados
                    if (masterDetail.Details.Where(detail => detail.GetType() == typeof(Report) || detail.GetType() == typeof(Filter)).Any(detail => HasCalculatedFields(detail)))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private bool HasAtLeastOneFormOrGridWithCalculatedField(UseCase useCase)
        {
            foreach (Screen screen in useCase.Screens)
            {
                //Para los paneles del tipo form o grid, si al menos uno tiene campos calculados
                if (screen.Panels.Where(panel => panel.GetType() == typeof(Form) || panel.GetType() == typeof(Grid)).Any(panel => HasCalculatedFields(panel)))
                {
                    return true;
                }

                //Se debe hacer lo mismo para los master details
                foreach (MasterDetail masterDetail in screen.MasterDetails)
                {
                    //Master del tipo Form o Grid que tiene campos calculados
                    if (masterDetail.Master.GetType() == typeof(Form) || masterDetail.Master.GetType() == typeof(Grid))
                    {
                        if (HasCalculatedFields(masterDetail.Master))
                        {
                            return true;
                        }
                    }

                    // Para cada panel de detalles si es del tipo form o grid y al menos uno tiene campos calculados
                    if (masterDetail.Details.Where(detail => detail.GetType() == typeof(Form) || detail.GetType() == typeof(Grid)).Any(detail => HasCalculatedFields(detail)))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        #endregion
    }
}